Skip to content

Conversation

@ilonatommy
Copy link
Member

@ilonatommy ilonatommy commented Dec 19, 2025

Add Label component for Blazor Forms

Add Label component that renders accessible labels with support for both nested and non-nested patterns

Description

This PR adds a new Label<TValue> component that renders an HTML <label> element with the display name extracted from [Display] or [DisplayName] attributes, falling back to the property name.

Details in #64791 (comment), #64791 (comment).

Usage

Nested pattern (wrapping):

<Label For="() => model.Name">
    <InputText @bind-Value="model.Name" />
</Label>

Renders:

<label>
    Name
    <input name="model.Name" ... />
</label>

Non-nested pattern (for/id):

<Label For="() => model.Name" />
<InputText @bind-Value="model.Name" />

Renders:

<label for="model.Name">Name</label>
<input id="model.Name" name="model.Name" ... />

Design

Supports both patterns for label-input association:

  1. Nested pattern: Input is wrapped inside the label, providing implicit HTML association without for/id matching
  2. Non-nested pattern: Label renders a for attribute that references the input's auto-generated id attribute

Changes

  • Label<TValue>: New component that conditionally renders for attribute when no ChildContent is provided
  • InputBase<TValue>.IdAttributeValue: New protected property that auto-generates id from the bound expression (follows same pattern as NameAttributeValue)
  • All input components (InputText, InputNumber, InputCheckbox, InputDate, InputSelect, InputTextArea, InputHidden): Now render id attribute using IdAttributeValue
  • Explicit for and id attributes in AdditionalAttributes take precedence over auto-generated values

API Changes

// New component
Microsoft.AspNetCore.Components.Forms.Label<TValue>
Microsoft.AspNetCore.Components.Forms.Label<TValue>.Label() -> void
Microsoft.AspNetCore.Components.Forms.Label<TValue>.For.get -> Expression<Func<TValue>>?
Microsoft.AspNetCore.Components.Forms.Label<TValue>.For.set -> void
Microsoft.AspNetCore.Components.Forms.Label<TValue>.ChildContent.get -> RenderFragment?
Microsoft.AspNetCore.Components.Forms.Label<TValue>.ChildContent.set -> void
Microsoft.AspNetCore.Components.Forms.Label<TValue>.AdditionalAttributes.get -> IReadOnlyDictionary<string, object>?
Microsoft.AspNetCore.Components.Forms.Label<TValue>.AdditionalAttributes.set -> void

// New protected property on InputBase<TValue>
Microsoft.AspNetCore.Components.Forms.InputBase<TValue>.IdAttributeValue.get -> string

Fixes #64791

@ilonatommy ilonatommy requested a review from javiercn December 19, 2025 12:44
@ilonatommy ilonatommy self-assigned this Dec 19, 2025
@ilonatommy ilonatommy requested a review from a team as a code owner December 19, 2025 12:44
Copilot AI review requested due to automatic review settings December 19, 2025 12:44
@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Dec 19, 2025
@ilonatommy ilonatommy added this to the .NET 11 Planning milestone Dec 19, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a new Label<TValue> component for Blazor Forms that renders an HTML <label> element with display name extraction from [Display] or [DisplayName] attributes. The component uses a wrapping pattern where input components are nested inside the label element, providing implicit HTML label-input association without requiring for/id attribute matching.

Key Changes

  • Added Label<TValue> component that implements IComponent for rendering accessible form labels
  • Component extracts display names using the existing ExpressionMemberAccessor.GetDisplayName utility
  • Comprehensive unit test suite covering various scenarios including attribute precedence, nested properties, and child content rendering

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Components/Web/src/Forms/Label.cs Implements the new Label<TValue> component with parameter handling, display name extraction, and render tree building
src/Components/Web/test/Forms/LabelTest.cs Adds comprehensive unit tests covering display name extraction, attributes, child content, and error scenarios
src/Components/Web/src/PublicAPI.Unshipped.txt Documents the new public API surface for Label<TValue> component

@ilonatommy ilonatommy marked this pull request as draft December 19, 2025 12:56
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Dec 27, 2025
@ilonatommy ilonatommy removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Jan 2, 2026
@ilonatommy ilonatommy requested a review from maraf January 6, 2026 11:36
@javiercn
Copy link
Member

javiercn commented Jan 9, 2026

There are two ways to use label isn't it?

  1. Wrapping input:
   <label>Text <input ...></label>
  1. Using "for":
   <label for="id">Text</label>
   <input id="id" ...> 

Is there a reason why we chose to implement only 1? Should we be detecting either of by checking to see if the RenderFragment has a value and using a form or the other appropriately?

@ilonatommy
Copy link
Member Author

ilonatommy commented Jan 12, 2026

Is there a reason why we chose to implement only 1?

Yes, breaking changes:

#64791 (comment)
#64791 (comment)

@javiercn
Copy link
Member

@ilonatommy thanks for the additional details.

I don't think the breaking change concern is a valid one for a couple of reasons:

  • for can be set earlier when we set parameters inside Label. If you apply an explicit for label, it overrides.
  • id/name can be applied in the same way to components. If a user explicitly sets name/id, it will take precedence.
    • We could choose to not even do this and force you to set the name on the component manually, even though it's subpar.

@ilonatommy
Copy link
Member Author

  • for can be set earlier when we set parameters inside Label. If you apply an explicit for label, it overrides.

We don't allow explicit for anymore, see #64821 (comment)

@javiercn
Copy link
Member

  • for can be set earlier when we set parameters inside Label. If you apply an explicit for label, it overrides.

We don't allow explicit for anymore, see #64821 (comment)

We should allow passing For and let the customer value override it like other components do. https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputBase.cs#L199-L221
https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputText.cs#L36-L37

(note that even though we set NameAttributeValue after AdditionalAttributes, we do check AdditionalAttributes on InputBase to account for its value).

@ilonatommy
Copy link
Member Author

  • for can be set earlier when we set parameters inside Label. If you apply an explicit for label, it overrides.

We don't allow explicit for anymore, see #64821 (comment)

Thank you, existing way of working is a good argument to not to throw, I am convinced.

The non-nested pattern is a bit more tricky, can we discuss it separately and if we find a consensus, add it in a follow up?

@ilonatommy
Copy link
Member Author

ilonatommy commented Jan 12, 2026

The non-nested pattern is a bit more tricky, can we discuss it separately and if we find a consensus, add it in a follow up?

We have InputText implement it the way you proposed (

if (AdditionalAttributes?.TryGetValue("name", out var nameAttributeValue) ?? false)
) but there's no other Blazor component generating a value that needs to match name. The name attribute is self-contained. If the user overrides it, they take full responsibility for server-side binding, but nothing in the framework breaks.

The Label + Input is not quite the same, it's 2 framework components that should coordinate. I think I got convinced that it's enough to document the coordination requirement.

@ilonatommy ilonatommy changed the title Implement nested Label for DisplayName Implement nested and non-nested Label support for DisplayName Jan 12, 2026
Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, only a few lose ends to track down, but other than that, it's good to go after we figure out that bit.

@ilonatommy ilonatommy enabled auto-merge (squash) January 14, 2026 13:52
@ilonatommy ilonatommy merged commit 23ce838 into dotnet:main Jan 14, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Label in addition to DisplayName

4 participants